//
//  HFCaptureView.m
//  HocusFocus
//
//  Created by Juan Carlos Jimenez on 4/22/08.
//  Copyright 2008 Juan Carlos Jimenez. All rights reserved.
//

#import "HFCaptureView.h"
#import "HFCaptureSession.h"
#import "HFPreferencesController.h"
#import "HFOrnament.h"
#import "HFBezierPathOrnament.h"
#import "HFGraphicProperties.h"

#define KEYCODE_ESC 53
#define KEYCODE_RETURN 36
#define KEYCODE_SPACE 49
#define KEYCODE_DEL 117
#define KEYCODE_BS 51

static NSCursor *captureCursor;
static NSShadow *cursorTextShadow;
static NSNumberFormatter *displayPointFormatter;
static NSPoint infoFieldOffsetFromCursor = { 4.0, -24.0 };

@implementation HFCaptureView

@synthesize captureSession;
@synthesize cursorInfoWindow;
@synthesize cursorInfoXField;
@synthesize cursorInfoYField;
@synthesize overlayPath;
@synthesize overlayInnerPath;
@synthesize overlayInnerPathShadow;

+ (void) initialize
{
    NSString *imageName = [[NSBundle mainBundle] pathForResource:@"CaptureCursor24x24" ofType:@"png"];
    NSImage *image = [[[NSImage alloc] initWithContentsOfFile:imageName] autorelease];
    captureCursor = [[NSCursor alloc] initWithImage:image hotSpot:NSMakePoint(12, 12)];

    cursorTextShadow = [[NSShadow alloc] init];
    [cursorTextShadow setShadowColor:[NSColor whiteColor]];
    [cursorTextShadow setShadowOffset:NSMakeSize(1.5, -1.5)];
    [cursorTextShadow setShadowBlurRadius:1.0];
    
    displayPointFormatter = [[NSNumberFormatter alloc] init];
    [displayPointFormatter setFormat:@"#,###"];
}

- (void)dealloc
{
    self.captureSession = nil;
    self.cursorInfoWindow = nil;
    [super dealloc];
}

- (void) resetCursorRects
{
    [self addCursorRect:[self bounds] cursor:captureCursor];
}

- (BOOL)acceptsFirstResponder
{
	return YES;
}

- (BOOL)becomeFirstResponder
{
    [self.cursorInfoWindow orderFront:self];
	return YES;
}

- (BOOL)resignFirstResponder
{
    [self.cursorInfoWindow orderOut:self];
	return YES;
}

- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
{
    return YES;
}

- (NSNotification *)captureNotificationWithName:(NSString *)name
{
    return [NSNotification notificationWithName:name
                                         object:self
                                       userInfo:[NSDictionary dictionaryWithObject:self.captureSession
                                                                            forKey:@"captureSession"]];
}

- (void)postCaptureWillDefineCaptureAreaNotification
{
    [[NSNotificationCenter defaultCenter] postNotification:[self captureNotificationWithName:kHFCaptureWillDefineCaptureAreaNotification]];
}

- (void)postCaptureDidDefineCaptureAreaNotification
{
    self.overlayPath = [NSBezierPath bezierPathWithRect:self.captureSession.captureArea.rect];
    [self.overlayPath setWindingRule:NSEvenOddWindingRule];
    [self.overlayPath appendBezierPathWithRect:[self bounds]];
    
    self.overlayInnerPath = [NSBezierPath bezierPathWithRect:self.captureSession.captureArea.rect];
    [self.overlayInnerPath setLineWidth:5];
    
    self.overlayInnerPathShadow = [[[NSShadow alloc] init] autorelease];
    [self.overlayInnerPathShadow setShadowColor:[NSColor keyboardFocusIndicatorColor]];
    [self.overlayInnerPathShadow setShadowOffset:NSMakeSize(0, 0)]; 
    [self.overlayInnerPathShadow setShadowBlurRadius:10.0];
    
    [[NSNotificationCenter defaultCenter] postNotification:[self captureNotificationWithName:kHFCaptureDidDefineCaptureAreaNotification]];
}

- (void)postCaptureDidEndNotification
{
    [[NSNotificationCenter defaultCenter] postNotification:[self captureNotificationWithName:kHFCaptureDoneNotification]];
    self.overlayPath = nil;
    self.overlayInnerPath = nil;
    self.overlayInnerPathShadow = nil;
}

- (void)postCaptureDidCancelNotification
{
    [[NSNotificationCenter defaultCenter] postNotification:[self captureNotificationWithName:kHFCaptureCancelNotification]];
    self.overlayPath = nil;
    self.overlayInnerPath = nil;
    self.overlayInnerPathShadow = nil;
}

- (BOOL)isOptionPressed:(NSEvent *)event
{
	return ([event modifierFlags] & NSAlternateKeyMask) != 0;
}

- (BOOL)isShiftPressed:(NSEvent *)event
{
	return ([event modifierFlags] & NSShiftKeyMask) != 0;
}

- (NSRect)redrawRectForFocusAreaRect:(NSRect)rect
{
    // total hack because the shapes draw shadows outside of their bounds
    return NSInsetRect(rect, -40.0, -40.0);
//    return rect;
}

- (NSRect)redrawRectForFocusArea:(HFFocusArea *)focusArea
{
    return [self redrawRectForFocusAreaRect:[focusArea rect]];
}

- (void)poof:(NSValue *)aPointValue {
    NSShowAnimationEffect(NSAnimationEffectPoof, [aPointValue pointValue], NSMakeSize(50.0, 50.0), nil, NULL, NULL);
}

- (void)poofAndRemoveFocusArea:(HFFocusArea *)focusArea
{
    NSPoint pt;
    
    [self setNeedsDisplayInRect:[self redrawRectForFocusArea:focusArea]];
    pt = NSMakePoint(focusArea.rect.origin.x + (focusArea.rect.size.width / 2.0), focusArea.rect.origin.y + (focusArea.rect.size.height / 2.0));
    pt = [[self window] convertBaseToScreen:[self convertPointToBase:pt]];
    [self.captureSession.focusAreas removeObject:focusArea];
    [self performSelector:@selector(poof:) withObject:[NSValue valueWithPoint:pt] afterDelay:0];
}

- (HFFocusArea *)focusAreaAtPoint:(NSPoint)pt
{
    HFFocusArea *result = nil;
    
    for (HFFocusArea *focusArea in [[captureSession focusAreas] reverseObjectEnumerator]) {
        HFOrnament *ornament = [focusArea ornament];
        
        if ([ornament hitTest:pt inRect:[focusArea rect]]) {
            result = focusArea;
            break;
        }
    }
    return result;
}

- (NSRect)rectForDragPoint:(NSPoint)dragPoint fromCenter:(BOOL)fromCenter forceSquare:(BOOL)forceSquare
{
	NSRect result;
	
    if (fromCenter) {
        CGFloat deltaX = abs(anchorPoint.x - dragPoint.x);
        CGFloat deltaY = abs(anchorPoint.y - dragPoint.y);
        
        if (forceSquare) {
            deltaX = deltaY = (deltaX < deltaY ? deltaX : deltaY);
        }
        
        result.origin.x = anchorPoint.x - deltaX;
        result.origin.y = anchorPoint.y - deltaY;
        
        // from center outward from anchor point
        result.size.width = deltaX * 2.0;
        result.size.height = deltaY * 2.0;
    } else {
        CGFloat deltaX = abs(anchorPoint.x - dragPoint.x);
        CGFloat deltaY = abs(anchorPoint.y - dragPoint.y);
        
        if (forceSquare) {
            deltaX = deltaY = (deltaX < deltaY ? deltaX : deltaY);
        }
        result.origin.x = (anchorPoint.x < dragPoint.x) ? anchorPoint.x : (anchorPoint.x - deltaX);
        result.origin.y = (anchorPoint.y < dragPoint.y) ? anchorPoint.y : (anchorPoint.y - deltaY);
        
        // The size should be the absolute difference between the two points
        result.size.width  = deltaX;
        result.size.height = deltaY;
    }
	return result;
}

- (NSPoint)displayPointForScreenPoint:(NSPoint)screenPt
{
    NSPoint result = [[self window] convertScreenToBase:screenPt];
    
    result.y = [[self window] frame].size.height - result.y;
    return result;
}

- (void)updateCursorPosition:(NSPoint)pt withDisplay:(NSPoint)visiblePt
{
    NSAttributedString *xValue = [[NSAttributedString alloc] initWithString:[displayPointFormatter stringFromNumber:[NSNumber numberWithInt:(int)visiblePt.x]]
                                                                 attributes:[NSDictionary dictionaryWithObject:cursorTextShadow
                                                                                                        forKey:NSShadowAttributeName]];
    NSAttributedString *yValue = [[NSAttributedString alloc] initWithString:[displayPointFormatter stringFromNumber:[NSNumber numberWithInt:(int)visiblePt.y]]
                                                                 attributes:[NSDictionary dictionaryWithObject:cursorTextShadow
                                                                                                        forKey:NSShadowAttributeName]];

    [self.cursorInfoXField setAttributedStringValue:xValue];
    [self.cursorInfoYField setAttributedStringValue:yValue];
    [xValue release];
    [yValue release];
    // offset from cursor hotspot
    pt.x += infoFieldOffsetFromCursor.x;
    pt.y += infoFieldOffsetFromCursor.y;
    [self.cursorInfoWindow setFrameOrigin:pt];
}

- (HFOrnament *)createCaptureAreaOrnament
{
    NSRect r = NSMakeRect(0, 0, 100, 100);
    HFOrnament *result = [[[HFBezierPathOrnament alloc] initWithBezier:[NSBezierPath bezierPathWithRect:r] inRect:r] autorelease];
    HFGraphicProperties *graphicProperties = [[[HFGraphicProperties alloc] init] autorelease];
    
    graphicProperties.strokeLineWidth = 0.5;
    graphicProperties.strokeColor = [[NSColor whiteColor] colorWithAlphaComponent:0.95];
    graphicProperties.fillColor = [[NSColor blackColor] colorWithAlphaComponent:0.10];
    graphicProperties.lineCapStyle = NSSquareLineCapStyle;
    graphicProperties.shadow = nil;
    result.graphicProperties = graphicProperties;
    return result;
}

- (void)flagsChanged:(NSEvent *)event
{
    if (self.captureSession.currentFocusArea != nil) {
        NSRect pr = self.captureSession.currentFocusArea.rect;
        
        self.captureSession.currentFocusArea.rect = [self rectForDragPoint:lastMousePoint fromCenter:[self isOptionPressed:event] forceSquare:[self isShiftPressed:event]];
        [self setNeedsDisplayInRect:[self redrawRectForFocusAreaRect:NSUnionRect(pr, captureSession.currentFocusArea.rect)]];
        if (([self.captureSession.focusAreas count] == 0) && [self.captureSession.captureArea isValid]) {
            // defining focus area, update size display
            NSRect r = self.captureSession.currentFocusArea.rect;

            [self updateCursorPosition:[NSEvent mouseLocation] withDisplay:NSMakePoint(r.size.width, r.size.height)];
        }
    }
}

- (void)keyDown:(NSEvent *)event
{
    // do nothing
}

- (void)keyUp:(NSEvent *)event
{
    switch ([event keyCode]) {
        case KEYCODE_ESC:
            [self postCaptureDidCancelNotification];
            break;
        case KEYCODE_RETURN:
        case KEYCODE_SPACE:
            [self postCaptureDidEndNotification];
            break;
        case KEYCODE_DEL:
        case KEYCODE_BS:
            if (self.captureSession.highlightedFocusArea != nil) {
                [self poofAndRemoveFocusArea:self.captureSession.highlightedFocusArea];
                self.captureSession.highlightedFocusArea = nil;
            }
            break;
        default:
            break;
    }
}

- (void)mouseMoved:(NSEvent *)event
{
    NSRect pr = NSZeroRect;
    BOOL updateRequired = captureSession.highlightedFocusArea != nil;
    
    if (updateRequired) {
        pr = self.captureSession.highlightedFocusArea.rect;
    }
    self.captureSession.highlightedFocusArea = nil;
    if (anchorPoint.x == -1) {
        // only hittest if we are not creating a new focus area
        if ((self.captureSession.highlightedFocusArea = [self focusAreaAtPoint:[event locationInWindow]]) != nil) {
            updateRequired = YES;
        }
    }
    if (updateRequired) {
        NSRect focusAreaRect = pr;
        
        if (self.captureSession.highlightedFocusArea != nil) {
            focusAreaRect = NSUnionRect(pr, self.captureSession.highlightedFocusArea.rect);
        }
        [self setNeedsDisplayInRect:[self redrawRectForFocusAreaRect:focusAreaRect]];
    }
    if (!(([self.captureSession.focusAreas count] == 0) && [self.captureSession.captureArea isValid])) {
        // nothing defined yet so show current position near cursor
        NSPoint screenPt = [[self window] convertBaseToScreen:[event locationInWindow]];

        [self updateCursorPosition:screenPt withDisplay:[self displayPointForScreenPoint:screenPt]];
    }
}

- (void)mouseDown:(NSEvent *)event
{
	anchorPoint = lastMousePoint = [event locationInWindow];
    if ((self.captureSession.movingFocusArea = [self focusAreaAtPoint:[event locationInWindow]]) == nil) {
        self.captureSession.currentFocusArea = [[[HFFocusArea alloc] init] autorelease];
        self.captureSession.currentFocusArea.rect = [self rectForDragPoint:[event locationInWindow]
                                                                fromCenter:[self isOptionPressed:event]
                                                               forceSquare:[self isShiftPressed:event]];
        if (self.captureSession.captureArea == nil) {
            // start to define capture area
            self.captureSession.currentFocusArea.ornament = [self createCaptureAreaOrnament];
            self.captureSession.captureArea = self.captureSession.currentFocusArea;
            [self postCaptureWillDefineCaptureAreaNotification];
        } else {
            self.captureSession.currentFocusArea.ornament = self.captureSession.currentOrnament;
        }
        if ([self.captureSession hasValidCaptureArea] && !NSPointInRect(anchorPoint, self.captureSession.captureArea.rect)) {
            [self postCaptureDidEndNotification];
        }
    }
}

- (void)mouseDragged:(NSEvent *)event
{
    NSPoint currentPoint = [event locationInWindow];
    HFFocusArea *movingFocusArea = self.captureSession.movingFocusArea;
    
    if (movingFocusArea == nil) {
        NSRect pr = self.captureSession.currentFocusArea.rect;
        
        self.captureSession.currentFocusArea.rect = [self rectForDragPoint:[event locationInWindow]
                                                                fromCenter:[self isOptionPressed:event]
                                                               forceSquare:[self isShiftPressed:event]];
        // defining capture area so keep updating the point
        if (([self.captureSession.focusAreas count] == 0) && [self.captureSession.captureArea isValid]) {
            NSRect r = self.captureSession.currentFocusArea.rect;

            [self updateCursorPosition:[[self window] convertBaseToScreen:[event locationInWindow]] withDisplay:NSMakePoint(r.size.width, r.size.height)];
        }
        [self setNeedsDisplayInRect:[self redrawRectForFocusAreaRect:NSUnionRect(pr, self.captureSession.currentFocusArea.rect)]];
    } else {
        NSRect pr = movingFocusArea.rect;
        
        // move focus area to new spot
        movingFocusArea.rect = NSMakeRect(movingFocusArea.rect.origin.x + (currentPoint.x - lastMousePoint.x),
                                          movingFocusArea.rect.origin.y + (currentPoint.y - lastMousePoint.y),
                                          movingFocusArea.rect.size.width,
                                          movingFocusArea.rect.size.height);
        [self setNeedsDisplayInRect:[self redrawRectForFocusAreaRect:NSUnionRect(pr, movingFocusArea.rect)]];
    }
    lastMousePoint = [event locationInWindow];
}

- (void)mouseUp:(NSEvent *)event
{
    HFFocusArea *movingFocusArea = self.captureSession.movingFocusArea;

    if (movingFocusArea == nil) {
        self.captureSession.currentFocusArea.rect = [self rectForDragPoint:[event locationInWindow]
                                                                fromCenter:[self isOptionPressed:event]
                                                               forceSquare:[self isShiftPressed:event]];
        if (self.captureSession.captureArea != self.captureSession.currentFocusArea) {
            [self.captureSession.focusAreas addObject:self.captureSession.currentFocusArea];
        }
        if ((self.captureSession.focusAreas.count == 0) && ![self.captureSession.currentFocusArea isValid]) {
            // click without any area for capture
            [self postCaptureDidCancelNotification];
        }
        if ((self.captureSession.focusAreas.count == 0) && [self.captureSession.currentFocusArea isValid]) {
            // we just defined the capture area
            [self postCaptureDidDefineCaptureAreaNotification];
        }
        self.captureSession.currentFocusArea = nil;
    } else {
        if (!NSIntersectsRect(movingFocusArea.rect, self.captureSession.captureArea.rect)) {
            [self poofAndRemoveFocusArea:self.captureSession.movingFocusArea];
        }
    }
    self.captureSession.movingFocusArea = nil;
    anchorPoint.x = lastMousePoint.x = anchorPoint.y = lastMousePoint.y = -1.0;
    [self setNeedsDisplayInRect:[self bounds]];
}


- (void)drawRect:(NSRect)rect {
    NSRect r = [self bounds];
    
    CGContextClearRect([[NSGraphicsContext currentContext] graphicsPort], CGRectMake(r.origin.x, r.origin.y, r.size.width, r.size.height));
    if (self.captureSession.captureArea != nil) {
        if ([self.captureSession isDefiningCaptureArea]) {
            [self.captureSession.currentFocusArea.ornament drawInRect:self.captureSession.currentFocusArea.rect];
        } else {
            [[NSGraphicsContext currentContext] saveGraphicsState];
            [[[NSColor blackColor] colorWithAlphaComponent:0.50] set];
            [self.overlayPath fill];
            [self.overlayInnerPathShadow set];
            [[self.overlayInnerPathShadow shadowColor] set];
            int i;
            for (i = 0; i < 8; i++) {
                [self.overlayInnerPath stroke];
            }
            
            NSRect cr = [self.overlayInnerPath bounds];
            CGContextClearRect([[NSGraphicsContext currentContext] graphicsPort], CGRectMake(cr.origin.x, cr.origin.y, cr.size.width, cr.size.height));
            
            [[NSGraphicsContext currentContext] restoreGraphicsState];
            for (HFFocusArea *focusArea in self.captureSession.focusAreas) {
                [focusArea.ornament drawInRect:focusArea.rect highlighted:(focusArea == self.captureSession.highlightedFocusArea)];
            }
            [self.captureSession.currentFocusArea.ornament drawInRect:self.captureSession.currentFocusArea.rect];
        }
    }
}

- (void)awakeFromNib
{
    [self.cursorInfoWindow setStyleMask:NSBorderlessWindowMask];
    [self.cursorInfoWindow setOpaque:NO];
    [self.cursorInfoWindow setBackgroundColor:[NSColor clearColor]];
    
    [self.cursorInfoXField setStringValue:@""];
    [self.cursorInfoXField setDrawsBackground:NO];
    [self.cursorInfoYField setStringValue:@""];
    [self.cursorInfoYField setDrawsBackground:NO];
}

@end
